// =============================================================================
//  STATS - Statistical Analysis Tools, v.0.01
//  Copyright (C) 2011  Marco Vettigli
// -----------------------------------------------------------------------------
//
// This program  is free software:  you can  redistribute it and/or  modify it
// under the terms of the  GNU General Public License as published by the Free
// Software Foundation,  either version 3 of the License,  or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY;  without even  the  implied  warranty  of  MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License  for
// more details.
//
// You  should  have received a copy of the  GNU General Public License  along
// with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// == module ===================================================================
//
// STABLE.CPP
// The class defines a model to store data in tables; it is the common data
// source for all tabular data management in StatS.
//
// Author: Marco Vettigli
//
// TODO: module documentation
//
// == implementation ===========================================================
//
// TODO: implamentation documentation
//
// =============================================================================

// == include directives =======================================================

#include <QFile>
#include <QTextStream>
#include <QIcon>
#include <QPainter>
#include <QPen>
#include <QDebug>
#include "stable.h"

// == constructors and destructors =============================================

StatS::STable::STable(const QString name) : QAbstractTableModel()
{
    // initialize cols and rows property vectors
    _colsProperties = QVector<StatS::SColProperty>();
    _rowsProperties = QVector<StatS::SRowProperty>();
    // initialize object private variable
    if (name.isEmpty()) _name = "Untitled";    
    else _name = name;    
    _filename = QString();
    _savedState = false;
    _untitledNumber = 1;
    _cols = 0;
    _rows = 0;
    // create new columns
    this->insertColumns(0, DEFAULT_COLS);
}

// == getters ==================================================================

QString StatS::STable::name() const
{
    return _name;
}

QString StatS::STable::fileName() const
{
    return _filename;
}

const QString StatS::STable::untitledColumnName()
{
    // check if untitled column name is present
    while (this->isColumnNameUsed(QString("Untitled%1").arg(_untitledNumber)))
        _untitledNumber++;
    // return unique untitled column name
    return QString("Untitled%1").arg(_untitledNumber);
}

int StatS::STable::rowCount(const QModelIndex& /*parent*/) const
{
    return _rows;
}

int StatS::STable::columnCount(const QModelIndex& /*parent*/) const
{
    return _cols;
}

Qt::ItemFlags StatS::STable::flags(const QModelIndex &index) const
{
    if (!index.isValid()) return Qt::ItemIsEnabled;
    return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}

QVariant StatS::STable::data(const QModelIndex &index,
                             int role) const
{
    // check if index is valid
    if (!isColumnIndex(index.column()) || !isRowIndex(index.row()))
        return QVariant();
    // get the property of indexed array
    StatS::SColProperty p = _colsProperties[index.column()];
    // set text aligment based on column type
    if (role == Qt::TextAlignmentRole) {
        switch (p.type()) {
        case StatS::SColProperty::SDoubles:
            return int(Qt::AlignRight | Qt::AlignVCenter);
        case StatS::SColProperty::SStrings:
            return int(Qt::AlignLeft | Qt::AlignVCenter);
        default: return QVariant();
        }
    }
    // define variables for casting
    double dValue = 0;
    QString sValue;
    // set data content
    if (role == Qt::DisplayRole) {
        switch (p.type()) {
        case StatS::SColProperty::SDoubles:
            dValue = ((StatS::SDoubles*)p.array())->get(index.row());
            if (dValue == StatS::SDoubles::null) return QVariant(".");
            else return QString::number(dValue);
        case StatS::SColProperty::SStrings:
            sValue = ((StatS::SStrings*)p.array())->get(index.row());
            if (sValue == StatS::SStrings::null) return QVariant();
            else return sValue;
        default: return QVariant();
        }
    }
    if (role == Qt::EditRole) {
        return dataString(index.row(), index.column());
    }
    if (role == Qt::BackgroundColorRole) {
        // full selection
        if (_colsProperties[index.column()].isSelected() &&
                _rowsProperties[index.row()].isSelected())
            return QColor(180, 183, 207);
        // partial selection
        if (_colsProperties[index.column()].isSelected() &&
                !_rowsProperties[index.row()].isSelected())
            return QColor(238, 240, 253);
        if (!_colsProperties[index.column()].isSelected() &&
                _rowsProperties[index.row()].isSelected())
            return QColor(238, 240, 253);
        // no selection
        return Qt::transparent;
    }    
    // else return null QVariant
    return QVariant();
}

QString StatS::STable::dataString(int row, int col) const
{
    // check if column and row arguments are valid
    if (!isColumnIndex(col)) return QString();
    if (!isRowIndex(row)) return QString();
    // define needed variables
    StatS::SDoubles* dArray = 0;
    StatS::SStrings* sArray = 0;
    // return data based on column type and content
    switch (_colsProperties[col].type()) {
    case StatS::SColProperty::SDoubles:
        dArray = (StatS::SDoubles*)_colsProperties[col].array();
        if (dArray->isNull(row)) return QString();
        else return QString::number(dArray->get(row));
    case StatS::SColProperty::SStrings:
        sArray = (StatS::SStrings*)_colsProperties[col].array();
        if (sArray->isNull(row)) return QString();
        else return sArray->get(row);
    default: return QString();
    }
}

QVariant StatS::STable::headerData(int section,
                                   Qt::Orientation orientation,
                                   int role) const
{
    // based on role request return proper data
    switch (role) {

    // set header data
    case Qt::DisplayRole:
        switch (orientation) {
        case Qt::Horizontal:
            if (!isColumnIndex(section)) return QVariant();
            switch(_colsProperties[section].type()) {
            case StatS::SColProperty::SDoubles:
                return ((StatS::SDoubles*)_colsProperties[section]
                        .array())->name();
            case StatS::SColProperty::SStrings:
                return ((StatS::SStrings*)_colsProperties[section]
                        .array())->name();
            default: return QVariant();
            }
        case Qt::Vertical:
            if (!isRowIndex(section)) return QVariant();
            return QVariant(section + 1);
        }

    // set tooltips
    case Qt::ToolTipRole:
        switch (orientation) {
        case Qt::Horizontal:
            return QString("%1%2").arg(getColumnName(section))
                    .arg(_colsProperties[section].getToolTip());
        case Qt::Vertical:
            return QVariant();
        }

    // set decoration
    case Qt::DecorationRole:
        switch (orientation) {
        case Qt::Horizontal:
            switch (_colsProperties[section].type()) {
            case StatS::SColProperty::SDoubles:
                return QIcon(":/images/numeric");
            case StatS::SColProperty::SStrings:
                return QIcon(":/images/character");
            default: return QVariant();
            }
        case Qt::Vertical:
            return QIcon(_rowsProperties[section].getPixmap());
        }

    // set text alignment
    case Qt::TextAlignmentRole:
        return int(Qt::AlignCenter | Qt::AlignVCenter);

    // set text font
    case Qt::FontRole:
        QFont font = QFont();
        switch (orientation) {
        case Qt::Horizontal:
            if (_colsProperties[section].isSelected())
                font.setBold(true);
            break;
        case Qt::Vertical:
            if (_rowsProperties[section].isSelected())
                font.setBold(true);
            break;
        default: return QVariant();
        }
        return font;
    }

    return QVariant();

}

QString StatS::STable::getColumnName(int col) const
{
    if (!isColumnIndex(col)) return QString();
    switch (_colsProperties[col].type()) {
    case StatS::SColProperty::SDoubles:
        return ((StatS::SDoubles*)_colsProperties[col].array())->name();
    case StatS::SColProperty::SStrings:
        return ((StatS::SStrings*)_colsProperties[col].array())->name();
    default: return QString();
    }
}

StatS::SColProperty::ArrayType StatS::STable::getColumnType(int col) const
{
    if (!isColumnIndex(col)) return StatS::SColProperty::Undefined;
    return _colsProperties[col].type();
}

const StatS::SColProperty* StatS::STable::getColumnProperty(int col) const
{
    // return reference to colProperty if index is valid
    if (isColumnIndex(col)) return &_colsProperties[col];
    else return 0;
}

const StatS::SRowProperty* StatS::STable::getRowProperty(int row) const
{
    // return reference to rowProperty if index is valid
    if (isRowIndex(row)) return &_rowsProperties[row];
    else return 0;
}

void * StatS::STable::getColumnReference(int col)
{
    // check if column index is valid
    if (!isColumnIndex(col)) return 0;
    // check if column type referenced by user is proper
    return _colsProperties[col].array();
}

int StatS::STable::getColumnIndex(void *array) const
{
    // find the index of array
    for (int i = 0; i < _cols; i++)
        if (_colsProperties[i].array() == array) return i;
    // else return -1 (not found)
    return -1;
}

int StatS::STable::getColumnIndex(const QString name) const
{
    // look for column name
    for (int i = 0; i < _cols; i++) {
        QString headerName = headerData(i, Qt::Horizontal).toString();
        if (headerName == name) return i;
    }
    // column name not found
    return -1;
}

// == setters ==================================================================

bool StatS::STable::setName(const QString name)
{
    if (name.isEmpty()) return false;
    _name = name;
    _savedState = false;
    return true;
}

bool StatS::STable::setFilename(const QString filename)
{
    // check if filename is valid
    if (filename.isEmpty()) return false;    
    // check if new file is different from the current one
    if (_filename == filename) return false;
    _filename = filename;
    return true;
}

bool StatS::STable::setData(const QModelIndex &index,
                            const QVariant &value,
                            int role)
{
    // check if column and row indexes are valid
    if (!index.isValid()) return false;
    // get the index of array by column index
    StatS::SColProperty p = _colsProperties[index.column()];
    // based on requested role, set the data
    if (role == Qt::EditRole) {
        // edit data based on column type
        switch (p.type()) {
        case StatS::SColProperty::SDoubles:
            ((StatS::SDoubles*)p.array())
                    ->assign(value.toString(), index.row());
            break;
        case StatS::SColProperty::SStrings:
            ((StatS::SStrings*)p.array())
                    ->assign(value.toString(), index.row());
            break;
        default: return false;
        }
        // emit dataChanged signal and return
        emit(dataChanged(index, index));
        _savedState = false;
        return true;
    }    
    return false;
}

bool StatS::STable::setHeaderData(int section,
                                  Qt::Orientation orientation,
                                  const QVariant &value,
                                  int role)
{
    // only columns can be renamed
    QString columnName = value.toString();
    // check if column index is valid
    if (!this->isColumnIndex(section)) return false;
    // check if column name is already used
    if (this->isColumnNameUsed(columnName)) return false;
    if (role != Qt::EditRole) return false;
    // set header name
    if (orientation == Qt::Horizontal) {
        switch(_colsProperties[section].type()) {
        case StatS::SColProperty::SDoubles:
            ((StatS::SDoubles*)_colsProperties[section].array())
                    ->setName(columnName);
            break;
        case StatS::SColProperty::SStrings:
            ((StatS::SStrings*)_colsProperties[section].array())
                    ->setName(columnName);
            break;
        default: return false;
        }
    }
    emit(headerDataChanged(orientation, section, section));
    _savedState = false;
    return true;
}

bool StatS::STable::setRowMarker(int row,
                                 StatS::SRowProperty::RowMarker marker)
{
    // check if row index is valid
    if (!isRowIndex(row)) return false;
    // set marker property, emit signal and return
    _rowsProperties[row].setMarker(marker);
    emit(headerDataChanged(Qt::Vertical, row, row));
    _savedState = false;
    return true;
}

bool StatS::STable::setRowColor(int row,
                                const QColor color)
{
    // check if row index is valid
    if (!isRowIndex(row)) return false;
    // set marker property, emit signal and return
    _rowsProperties[row].setColor(color);
    emit(headerDataChanged(Qt::Vertical, row, row));
    _savedState = false;
    return true;
}

bool StatS::STable::setRowSize(int row,
                               int size)
{
    // check if row index and size are valid
    if (!isRowIndex(row)) return false;
    if (size < 0 || size > 6) return false;
    // set marker property, emit signal and return
    _rowsProperties[row].setSize(size);
    emit(headerDataChanged(Qt::Vertical, row, row));
    _savedState = false;
    return true;
}

bool StatS::STable::setColumnDescription(int col,
                                         const QString description)
{
    // check if column index is valid
    if (!isColumnIndex(col)) return false;
    // change column description and return
    _colsProperties[col].setDescription(description);
    emit(headerDataChanged(Qt::Horizontal, col, col));
    _savedState = false;
    return true;
}

bool StatS::STable::setColumnUnits(int col,
                                   const QString units)
{
    // check if row index is valid
    if (!isColumnIndex(col)) return false;
    // change column description and return
    _colsProperties[col].setUnits(units);
    emit(headerDataChanged(Qt::Horizontal, col, col));
    _savedState = false;
    return true;
}

bool StatS::STable::setColumnUsl(int col,
                                 double usl)
{
    // check if column index is valid
    if (!isColumnIndex(col)) return false;
    // change column description
    _colsProperties[col].setUsl(usl);
    emit(headerDataChanged(Qt::Horizontal, col, col));
    _savedState = false;
    return true;
}

bool StatS::STable::setColumnLsl(int col, double lsl)
{
    // check if column index is valid
    if (!isColumnIndex(col)) return false;
    // change column description
    _colsProperties[col].setLsl(lsl);
    emit(headerDataChanged(Qt::Horizontal, col, col));
    _savedState = false;
    return true;
}

// == inspectors ===============================================================

bool StatS::STable::isDataSaved() const
{
    return _savedState;
}

bool StatS::STable::isColumnIndex(int col) const
{
    return (col >= 0 && col < _cols);
}

bool StatS::STable::isRowIndex(int row) const
{
    return (row >= 0 && row < _rows);
}

bool StatS::STable::isColumnNameUsed(const QString name) const
{
    // browse column name if there is any matching
    for (int i = 0; i < _cols; i++) {
        QString headerName = headerData(i, Qt::Horizontal).toString();
        if (headerName == name) return true;
    }
    // column name not found
    return false;
}

bool StatS::STable::isColumnConvertable(int col,
                                        StatS::SColProperty::ArrayType type)
const
{
    // check if column index is valid
    if (!isColumnIndex(col)) return false;
    // check if new column type is different from current one
    StatS::SColProperty::ArrayType currentType = this->getColumnType(col);
    if (currentType == type) return true;
    // define variables
    bool test = true;
    //StatS::SDoubles* dArray; // no convertion check needed
    StatS::SStrings* sArray;
    // based on requested column type, check data loss
    switch (type) {
    case StatS::SColProperty::SDoubles:
    // based on current column type, populate the array
        switch (currentType) {
        case StatS::SColProperty::SStrings:
            for (int i = 0; i < _rows; i++) {
                // check if character value is convertable to double (or null)
                sArray = (StatS::SStrings*)_colsProperties[col].array();
                if (sArray->isNull(i)) continue;
                sArray->get(i).toDouble(&test);
                if (test) continue;
                else return false;
            }
            break;
        default: return false;
        }
        break;
    case StatS::SColProperty::SStrings:
        switch (currentType) {
        case StatS::SColProperty::SDoubles:
            /*-----------------------------------------------------------
            // no convertion needed, structure left for further
            // implementation
            -------------------------------------------------------------
            for (int i = 0; i < rows; i++) {
                dArray = (StatS::SDoubles*)colsProperties[column].array;
                ...
            }
            -----------------------------------------------------------*/
            break;
        default: return false;
        }
        break;
    default: return false;
    }
    return test;
}

// == accessor function definitions ============================================

// == column accessors =========================================================

bool StatS::STable::convertColumn(int col,
                                  StatS::SColProperty::ArrayType type)
{
    // check if column index is valid
    if (!isColumnIndex(col)) return false;
    // get current column type and position
    StatS::SColProperty::ArrayType currentType = getColumnType(col);
    StatS::SDoubles* dArray;
    StatS::SStrings* sArray;
    // check if requested type is different from current one
    if (currentType == type) return false;
    // create new column and populate it with data
    switch (type) {
    case StatS::SColProperty::SDoubles:
        // create new double array
        dArray = new StatS::SDoubles();
        // based on current column type, populate the array
        switch (currentType) {
        case StatS::SColProperty::SStrings:
            // populate new column
            for (int i = 0; i < _rows; i++)
                dArray->append(((StatS::SStrings*)_colsProperties[col].array())
                               ->get(i));
            // rename new column
            dArray->setName(((StatS::SStrings*)_colsProperties[col].array())
                            ->name());
            // delete old column
            delete (StatS::SStrings*)_colsProperties[col].array();
            _colsProperties[col].setArray((void*)dArray);
            break;
        default: return false;
        }
        break;
    case StatS::SColProperty::SStrings:
        // create new double array
        sArray = new StatS::SStrings();
        // based on current column type, populate the array
        switch (currentType) {
        case StatS::SColProperty::SDoubles:
            // populate new column
            for (int i = 0; i < _rows; i++)
                sArray->append(((StatS::SDoubles*)_colsProperties[col].array())
                               ->get(i));
            // rename new column
            sArray->setName(((StatS::SDoubles*)_colsProperties[col].array())
                            ->name());
            // delete old column
            delete (StatS::SDoubles*)_colsProperties[col].array();
            _colsProperties[col].setArray((void*)sArray);
            break;
        default: return false;
        }
        break;
    default: return false;
    }
    _colsProperties[col].setType(type);

    // emit headerDataChanged signal and return
    emit(headerDataChanged(Qt::Horizontal, col, col));
    _savedState = false;
    return true;

}

bool StatS::STable::insertColumns(int col,
                                  int n,
                                  StatS::SColProperty::ArrayType type,
                                  QString name,
                                  const QModelIndex& /*parent*/)
{
    // check if column index and cout are valid and if STable is empty
    if (!isColumnIndex(col) && col != _cols) return false;
    if (n <= 0) return false;
    // if no column exists set rows number to default and
    // initialize rowProperties
    if (_rows == 0) {
        _rows = DEFAULT_ROWS;
        for (int i = 0; i < _rows; i++)
            _rowsProperties.append(SRowProperty());
    }
    // emit signal for begin of column insertion
    beginInsertColumns(QModelIndex(), col, col + n - 1);
    // define property for new column
    StatS::SColProperty new_property = StatS::SColProperty();
    StatS::SDoubles* dArray;
    StatS::SStrings* sArray;

    // insert columns
    for (int i = 0; i < n; i++) {
        // define variables and common properties
        QString column_name = name;
        new_property.setType(type);
        // check if the column insertion must be enumerated
        if (n != 1) column_name = QString("%1%2").arg(name).arg(i+1);
        // check if column name is valid else provide default
        if (name.isEmpty() || isColumnNameUsed(column_name)) {
            // assign unique untitled column name
            column_name = untitledColumnName();
            _untitledNumber++;
        }
        // insert column based on its type
        switch (type) {
        case StatS::SColProperty::SDoubles:
            // create double column and fill with it with null values
            dArray = new StatS::SDoubles(column_name);
            dArray->append(StatS::SDoubles::null, _rows);
            new_property.setArray((void*)dArray);
            break;
        case StatS::SColProperty::SStrings:
            // create string column and fill with it with null values
            sArray = new StatS::SStrings(column_name);
            sArray->append(StatS::SStrings::null, _rows);
            new_property.setArray((void*)sArray);
            break;
        default: break;
        }
        // insert column property and update number of columns
        if (col + i == _cols) _colsProperties.append(new_property);
        else _colsProperties.insert(col + i, new_property);
        _cols++;
    }
    // emit signal for end of column insertion and return
    endInsertColumns();
    emit(headerDataChanged(Qt::Horizontal, col, col + n - 1));
    _savedState = false;
    return true;
}

bool StatS::STable::insertColumn(int col,
                                 const QModelIndex& /*parent*/)
{
    return insertColumns(col, 1, StatS::SColProperty::SStrings, 0);
}

bool StatS::STable::insertColumn(int col,
                                 StatS::SColProperty::ArrayType type,
                                 QString name,
                                 const QModelIndex& /*parent*/)
{
    return insertColumns(col, 1, type, name);
}

bool StatS::STable::insertColumns(int col,
                                  int n,
                                  const QModelIndex& /*parent*/)
{
    return insertColumns(col, n, StatS::SColProperty::SStrings, 0);
}

bool StatS::STable::removeColumns(int col,
                                  int n,
                                  const QModelIndex& /*parent*/)
{
    // check if arguments are valid
    if (!isColumnIndex(col)) return false;
    if (n < 1) return false;
    if (col + n > _cols) return false;
    if (n == _cols) return false;
    // remove columns
    beginRemoveColumns(QModelIndex(), col, col + n - 1);
    for (int i = col + n - 1; i >= col; i--) {
        switch (_colsProperties[i].type()) {
        case StatS::SColProperty::SDoubles:
            delete (StatS::SDoubles*)_colsProperties[i].array();
            break;
        case StatS::SColProperty::SStrings:
            delete (StatS::SStrings*)_colsProperties[i].array();
            break;
        case StatS::SColProperty::Undefined: return false;
        }
        _colsProperties.remove(i);
    }
    _cols -= n;
    // emit signal and return
    endRemoveColumns();
    emit(headerDataChanged(Qt::Horizontal, col, col + n - 1));
    _savedState = false;
    return true;
}

bool StatS::STable::removeColumn(int col,
                                 const QModelIndex& /*parent*/)
{
    return removeColumns(col, 1);
}

// == row accessors ============================================================

bool StatS::STable::insertRows(int row,
                               int n,
                               const QModelIndex& /*parent*/)
{
    // check if arguments are valid
    if (!isRowIndex(row) && row != _rows) return false;
    if (n < 1) return false;
    // state beginning of rows insertion
    beginInsertRows(QModelIndex(), row, row + n - 1);
    // span each column and insert n number of row based on column type
    // by default null values are inserted
    for (int i = 0; i < _cols; i++) {
        switch(_colsProperties[i].type()) {
        case StatS::SColProperty::SDoubles:
            ((StatS::SDoubles*)_colsProperties[i].array())
                    ->insert(StatS::SDoubles::null, row, n);
            break;
        case StatS::SColProperty::SStrings:
            ((StatS::SStrings*)_colsProperties[i].array())
                    ->insert(StatS::SStrings::null, row, n);
            break;
        default: break;
        }
    }
    // update row properties and number
    _rowsProperties.insert(row, n, SRowProperty());
    _rows += n;
    // state end of rows insertion and return
    endInsertRows();
    emit(headerDataChanged(Qt::Vertical, row, row + n - 1));
    _savedState = false;
    return true;
}

bool StatS::STable::insertRow(int row, const QModelIndex& /*parent*/)
{
    return insertRows(row, 1);
}

bool StatS::STable::removeRows(int row,
                               int n,
                               const QModelIndex& /*parent*/)
{
    // check if arguments are valid
    if (!isRowIndex(row)) return false;
    if (n < 1) return false;
    if (row + n > _rows) return false;
    if (n == _rows) return false;
    // state beginning of rows deletion
    beginRemoveRows(QModelIndex(), row, row+n-1);
    // span each column and remove n number of row based on column type
    for (int i = 0; i < _cols; i++) {
        switch(_colsProperties[i].type()) {
        case StatS::SColProperty::SDoubles:
            ((StatS::SDoubles*)_colsProperties[i].array())->remove(row, n);
            break;
        case StatS::SColProperty::SStrings:
            ((StatS::SStrings*)_colsProperties[i].array())->remove(row, n);
            break;
        default: break;
        }
    }
    // update row properties and number
    _rowsProperties.remove(row, n);
    _rows -= n;
    // state end of rows insertion and return
    endRemoveRows();
    emit(headerDataChanged(Qt::Vertical, row, row + n - 1));
    _savedState = false;
    return true;
}

bool StatS::STable::removeRow(int row, const QModelIndex& /*parent*/)
{
    return removeRows(row, 1);
}

// == selection accessors ======================================================

const QList<int> StatS::STable::selectedColumns() const
{
    // define list of ints
    QList<int> selected = QList<int>();
    // fill it with column indexes if columns are selected
    for(int i = 0; i < _cols; i++)
        if (_colsProperties[i].isSelected()) selected.append(i);

    return selected;
}

const QList<int> StatS::STable::selectedRows() const
{
    // define list of ints
    QList<int> selected = QList<int>();
    // fill it with rows indexes if rows are selected
    for(int i = 0; i < _rows; i++)
        if (_rowsProperties[i].isSelected()) selected.append(i);
    return selected;
}

bool StatS::STable::selectColumn(int col, bool allSelect)
{
    // check if column index is valid
    if (!isColumnIndex(col)) return false;
    // switch column selection state
    if (!_colsProperties[col].isSelected())
        _colsProperties[col].setSelected(true);
    else if (!allSelect)
        _colsProperties[col].setSelected(false);
    emit(headerDataChanged(Qt::Horizontal, col, col));    
    return true;
}

bool StatS::STable::selectColumns(QList<int> list, bool allSelect)
{
    blockSignals(true);
    if (list.size()) foreach (int col, list) selectColumn(col, allSelect);
    else {
        bool allSelected = selectedColumns().size() == _cols;
        for (int i = 0; i < _cols; i++) selectColumn(i, !allSelected);
    }
    blockSignals(false);
    emit(headerDataChanged(Qt::Horizontal, 0, _cols));
    return true;
}

bool StatS::STable::selectRow(int row, bool allSelect)
{
    // check if row index is valid
    if (!isRowIndex(row)) return false;
    // switch row selection state
    if (!_rowsProperties[row].isSelected())
        _rowsProperties[row].setSelected(true);
    else if (!allSelect)
        _rowsProperties[row].setSelected(false);
    emit(headerDataChanged(Qt::Vertical, row, row));    
    return true;
}

bool StatS::STable::selectRows(QList<int> list, bool allSelect)
{
    blockSignals(true);
    if (list.size()) foreach (int row, list) selectRow(row, allSelect);
    else {
        bool allSelected = selectedRows().size() == _rows;
        for (int i = 0; i < _rows; i++) selectRow(i, !allSelected);
    }
    blockSignals(false);
    emit(headerDataChanged(Qt::Vertical, 0, _rows));
    return true;
}

void StatS::STable::clearColumnSelection()
{
    // clear all column selection states
    for(int i = 0; i < _cols; i++) {
        _colsProperties[i].setSelected(false);
    }
    emit(headerDataChanged(Qt::Horizontal, 0, _cols));    
    return;
}

void StatS::STable::clearRowSelection()
{
    // clear all row selection states
    for(int i = 0; i < _rows; i++) {
        _rowsProperties[i].setSelected(false);
    }
    emit(headerDataChanged(Qt::Vertical, 0, _rows));    
    return;
}

void StatS::STable::clearSelection()
{
    clearColumnSelection();
    clearRowSelection();    
    return;
}

bool StatS::STable::save()
{
    // check saveState of the table
    if (_savedState) return true;
    // TODO: to be checked
    QFile* file = new QFile(_filename);
    if (!file->open(QFile::WriteOnly)) return false;
    QTextStream* out = new QTextStream(file);
    // save table and column names
    *out << name() << "\n";    
    for (int i = 0; i < _cols; i++) {
        *out << parsedString(getColumnName(i));
        if (i != _cols - 1) *out << ',';
        else *out << endl;
    }
    // save column properties
    for (int i = 0; i < _cols; i++) {
        *out << "{" << _colsProperties[i].type()
             << "," << parsedString(_colsProperties[i].description())
             << "," << parsedString(_colsProperties[i].units())
             << "," << _colsProperties[i].lsl()
             << "," << _colsProperties[i].usl()
             << "}";
        if (i != _cols - 1) *out << ',';
        else *out << endl;
    }    
    // save row properties
    for (int i = 0; i < _rows; i++) {
        *out << "{" << _rowsProperties[i].marker()             
             << "," << _rowsProperties[i].color().name()
             << "," << _rowsProperties[i].size()
             << "," << _rowsProperties[i].width()
             << "}";
        if (i != _rows - 1) *out << ',';
        else *out << endl;
    }    
    // save table data
    for (int i = 0; i < _cols; i++) {        
        for (int j = 0; j < _rows; j++) {            
            *out << parsedString(dataString(j, i));
            if (j != _rows - 1) *out << ',';
            else *out << endl;
        }
    }
    // close the file and dispose stream
    file->close();
    delete out;
    // set saveState and return
    _savedState = true;
    return true;
}

// == static variables =========================================================

// == static funtion definitions ===============================================

// == private function definitions =============================================

QString StatS::STable::parsedString(QString data) const
{
    // double " if any is present
    data.replace(QChar('"'), "\"\"");
    // append and prepend double-quotes if special characters are present
    if (data.contains(QChar(',')) || data.contains(QChar('"')))
        data.append(QChar('"')).prepend(QChar('"'));
    // return parsed string
    return data;
}
